package com.GPS_based_training.custom_components;


/*
 * The Android chronometer widget revised so as to count milliseconds
 */

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.View;
import java.text.DecimalFormat;
import com.GPS_based_training.progetto_so2_2014_2015.R;

/**
 * Class that defines a chronometer that shows milliseconds. 
 * @author Andreas Scalas
 */
public class TrainingChronometer extends View {

	/**
	 * @author Andreas Scalas
	 * 
	 * Interface that defines what method has to be implemented to define a listener for the chronometer tick.
	 * @author Andreas Scalas
	 */
    public interface OnChronometerTickListener {
    	/**
    	 * The sign of the method.
    	 * @param chronometer the chronometer on which set the listener.
    	 */
        void onChronometerTick(TrainingChronometer chronometer);
    }
    
    private String textHours;										//The text that shows the hours.
    private String textMinutes;										//The text that shows the minutes.
    private String textSeconds;										//The text that shows the seconds.
    private String textMilliSeconds;    							//The text that shows the milliseconds.
    
    private long mBase;												//The base of milliseconds at which the timing process starts.
    private boolean mVisible;										//Shows if the chronometer is visible.
    private boolean mStarted;										//Shows if the chronometer is started.
    private boolean mRunning;										//Shows if the chronometer is running.
    
    private OnChronometerTickListener mOnChronometerTickListener;	//The listener for the chronometer tick.

    private static final int TICK_WHAT = 2;							//Identifies what type of message has to be sent.
    
    private final String INIT_VALUE = "00";							//The text that is shown initially for hours, minutes and seconds.
    private final String INIT_VALUE_MILLISECONDS = "000";			//The text that is shown initially for minutes.
    private final int INIT_DIMENSION = 80;							//The text size for hours, minutes and seconds.
    private final int INIT_DIMENSION_MILLISECONDS = 40;				//The text size for milliseconds.
    private final int INIT_COLOR = 0x00CC00;						//The text color set by default.
    private final int MILLISECONDS_PER_HOUR = 1000 * 60 * 60;		//Defines how many milliseconds compound an hour. 
    private final int MILLISECONDS_PER_MINUTE = 1000 * 60;			//Defines how many milliseconds compound a minute.
    private final int MILLISECONDS_PER_SECOND = 1000;				//Defines how many milliseconds compound a second.
    private final int DELAY = 10;									//The delay of the message to be sent.
    private final int HEXADECIMAL_BASE = 16;						//Define the hexadecimal base.
    private final int HOUR_MINUTE_WIDTH = 110;						//Define the width of the text that shows hours and minutes, singly.
    private final int SECOND_WIDTH = 90;							//Define the width of the text that shows seconds.
    private final int TEXT_WIDTH = 310;								//Arbitrary defined width of the chronometer text.
    private final int SHIFT = 10;									//Arbitrary defined shift. 
    private final int RED_POSITION = 4;								//Position of the red chromatic component.
    private final int GREEN_POSITION = 2;							//Position of the green chromatic component. 
    private final int OPACITY = 0xFF;								//Defines the opacity
    private final float SHIFT_RATIO = (float) (3.0 / 8.0);			//The ratio between the text size and the shift.
    
	private int textColor;											//Tracks that color of the text.
	
	private float x;												//The position on x-coordinate of the view.
	private float y;												//The position on y-coordinate of the view.

    private long timeElapsed;										//Tracks the time elapsed since the start button has been clicked.
    private long timeWhenPaused; 									//Tracks the time elapsed since when the chronometer has benn stopped.
    
    private Paint paint; 											//Paint that have to be inserted into the canvas.
    
    /**
     * @author Andreas Scalas
     * 
     * Simple constructor based only on the context.
     * @param context the context.
     */
    public TrainingChronometer(Context context) {
    	
        this (context, null, 0);
        
    }
    
    /**
     * @author Andreas Scalas
     * 
     * Constructor based on context and on a set of attributes.
     * @param context the context.
     * @param attrs the set of attributes.
     */
    public TrainingChronometer(Context context, AttributeSet attrs) {
    	
        this (context, attrs, 0);
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Constructor based on context, on a set of attributes and on a defined style.
     * It is called by the other two constructors.
     * @param context the context.
     * @param attrs the set of attributes.
     * @param defStyle the stile.
     */
    public TrainingChronometer(Context context, AttributeSet attrs, int defStyle) {
    	
        super (context, attrs, defStyle); //Calls the parent's constructor.
        
        this.obtainAttributes(context, attrs);	//Obtain the attributes of the view.
        
        init(context); //Initialize some properties of the view.
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Method that initializes some properties of the view.
     * @param context the context of the view.
     */
    private void init(Context context) {
        
    	//Initializes the texts.
        textHours = "".concat(INIT_VALUE);
        textMinutes = "".concat(INIT_VALUE);
        textSeconds = "".concat(INIT_VALUE);
        textMilliSeconds = "".concat(INIT_VALUE_MILLISECONDS);
        
        //Initializes the paint.
        paint = new Paint();
        paint.setAntiAlias(true);
        
        //Set the base of milliseconds at which the timing process starts. 
        mBase = SystemClock.elapsedRealtime();
        
        //The chronometer is not in pause, so it has to be zeroed
        timeWhenPaused = 0;
        
        //Update the text to be shown.
        updateText(mBase);
        
    }

    /** 
     * @author Andreas Scalas
     * 
     * Method that sets the color of the view in base of the textColor attribute.
     * @param context the context of the view.
     * @param attrs attributes of the view.
     */
    private void obtainAttributes(Context context, AttributeSet attrs){
    	
    	//Obtains all the attributes defined in R.styleable.Chronometer
    	TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TrainingChronometer );
     	int attr; //The id of the attribute.
 	    final int N = typedArray.getIndexCount(); //Gets the number of the attributes.
 	    
 	    //Cycling all the attributes defined in R.styleable.Chronometer
        for (int i = 0; i < N; i++){

 	    	attr = typedArray.getIndex(i); //Saves in attr the actual attribute.
 	
 	    	switch (attr){
 	    	
 	    		//The id of the textColor attribute.
 	        	case R.styleable.TrainingChronometer_textColor:
 	        		
 	        		textColor = typedArray.getInteger(attr,  INIT_COLOR); //Sets the text color of the chronometer.
 	        		break;
 	        		
 	        	//Not known attribute.
 	        	default:
 	        		break;
 	    	
    		}
 	    	
        }
        
        typedArray.recycle(); //Prepare the array for the garbage collector
    }   
    
    /**
     * @author Andreas Scalas
     * 
     * Method that allows to set the base of milliseconds at which the timing process starts.
     * @param base the new base
     */
    public void setBase(long base) {
    	
        mBase = base; //Sets the new base.
        dispatchChronometerTick(); //Dispatch the tick.
        updateText(SystemClock.elapsedRealtime()); //Update the text to the new time base.
        
    }

    /**
     * @author Andreas Scalas
     * 
     * The GET method for mbase
     * @return the time base.
     */
    public long getBase() {
    	
        return mBase;
        
    }

    /**
     * @author Andreas Scalas
     * 
     * The SET method for the mOnChronometerListener.
     * @param listener the listener. 
     */
    public void setOnChronometerTickListener( OnChronometerTickListener listener) {
    	
        mOnChronometerTickListener = listener;
        
    }

    /**
     * @author Andreas Scalas
     * 
     * The GET method for mOnChronometerListener.
     * @return the listener.
     */
    public OnChronometerTickListener getOnChronometerTickListener() {
    	
        return mOnChronometerTickListener;
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Method that starts the chronometer.
     */
    public void start() {
    	
    	if(timeWhenPaused==0){
    		
    		//If the chronometer is not in pause.
    		mBase = SystemClock.elapsedRealtime(); //Set the time base.
    		
    	} else {
    		
    		//else.
    		mBase = SystemClock.elapsedRealtime() - timeWhenPaused; //Set the time base.
    		timeWhenPaused = 0; //The chronometer is no more paused, so it has to be zeroed. 
    		
    	}
        mStarted = true; //The chronometer is started.
        mRunning = true; //The chronometer is running.
        updateRunning(); //Update the status in running mode.
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Method that stops the chronometer.
     */
    public void stop() {
    	
        mStarted = false; //The chronometer is no more started.
        
        //The chronometer is no more paused, so it has to be zeroed.
        timeWhenPaused = 0; 
        textHours = "00";										
        textMinutes = "00";									
        textSeconds = "00";									
        textMilliSeconds = "000";    							
        
        updateRunning();
        
    }
    
    /**
     * @author Andreas Scalas
     * 
     * Method that put on pause the chronometer.
     */
    public void pause(){
    	timeWhenPaused = timeElapsed;
    	mRunning = false;
        updateRunning();
    }

    /**
     * @author Andreas Scalas
     * 
     * The SET method for mStarted
     * @param started is the chronometer started?
     */
    public void setStarted(boolean started) {
        mStarted = started;
        updateRunning();
    }
    
    /**
     * @author Andreas Scalas
     * 
     * The GET method for mStarted
     * @return if the chronometer is started.
     */
    public boolean getStarted(){
    	return mStarted;
    }
    
    /**
     * @author Andreas Scalas
     * 
     * Method that override the onDetachedFromWindow method from the parent. 
     */
    @Override
    protected void onDetachedFromWindow() {
    	
        super.onDetachedFromWindow();
        mVisible = false; //The chonometer is no long visible.
        updateRunning(); //Update in running mode.	
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Method that override the onWindowVisibilityChanged method from the parent. 
     */
    @Override
    protected void onWindowVisibilityChanged(int visibility) {
    	
        super.onWindowVisibilityChanged(visibility);
        
        mVisible = visibility==VISIBLE; //Check if the view is visible.
        updateRunning(); //Update in running mode.
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Method that allows the update of the text to be shown.
     * @param now the count of milliseconds.
     */
    private synchronized void updateText(long now) {
    	
        timeElapsed = now - mBase; //Check how many time is passed since the start button has been clicked.
        
        DecimalFormat decimalFormat = new DecimalFormat(INIT_VALUE); //Define digit format for hours, minutes and seconds.
        DecimalFormat decimalFormatMilliseconds = new DecimalFormat(INIT_VALUE_MILLISECONDS); //Define digit format for milliseconds.
        
        int hours = (int)(timeElapsed / (MILLISECONDS_PER_HOUR)); //Extrapolates the hours from the milliseconds count.
        int remaining = (int)(timeElapsed % (MILLISECONDS_PER_HOUR)); //Deletes the hours from the milliseconds count.
        int minutes = remaining / (MILLISECONDS_PER_MINUTE); //Extrapolates the minutes from the milliseconds count.
        remaining = remaining % (MILLISECONDS_PER_MINUTE); //Deletes the minutes from the milliseconds count.
        int seconds = remaining / MILLISECONDS_PER_SECOND; //Extrapolates the seconds from the milliseconds count.
        remaining = remaining % MILLISECONDS_PER_SECOND; //Deletes the seconds from the milliseconds count.
        int milliseconds = remaining; //Remaining milliseconds
        
        textHours="".concat(decimalFormat.format(hours).concat(":")); //Define the hours text
        textMinutes = "".concat(decimalFormat.format(minutes).concat(":")); //Define the minutes text
        textSeconds = "".concat(decimalFormat.format(seconds).concat(":")); //Define the seconds text
        textMilliSeconds = "".concat(decimalFormatMilliseconds.format(milliseconds)); //Define the milliseconds text
        
        this.invalidate(); 
        
    }

    /**
     * @author Andreas Scalas
     * 
     * Method hat update the chronometer on running.
     */
    private void updateRunning() {
    	
        boolean running = mVisible && mStarted; //The chronometer will run only if visible and started.
        
        if (running == isRunning()) {
        	
        	//In case the chronometer is in running mode...
            if (running) {
            	
            	/*...and it is running, it updates the text and check if there are messages and, if it is the case,
            	 * send it to the handler, with a DELAY time.
            	 */
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), DELAY);
                
            } else {
            	
            	//...and it is not running, removes the message 
                mHandler.removeMessages(TICK_WHAT);
                
            }
            
            /* If the chronometer is in running mode and it is not running, it exits running mode.
             * If the chronometer is in running mode and it is running, it remains in running mode.
             */
            setmRunning(running); 
        }
        
    }

    //The message handler of the chronometer.
    private Handler mHandler = new Handler() {
    	
    	/**
    	 * @author Andreas Scalas
    	 * Method that handle message received by the updateRunning method.
    	 */
        public void handleMessage(Message m) {
        	
            if (isRunning()) {
            	
            	/*If the chronometer is on running mode, it updates the text and check if there are messages and, 
            	* if it is the case, send it to the itself, with a DELAY time.
            	*/
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                sendMessageDelayed(Message.obtain(this , TICK_WHAT), DELAY);
                
            }
            
        }
        
    };

    /**
     * @author Andreas Scalas
     * Method that dispatches the tick of the chronometer.
     */
    void dispatchChronometerTick() {
    	
        if (mOnChronometerTickListener != null) {
        	
        	//If the listener does exist, throws an event to it.
        	mOnChronometerTickListener.onChronometerTick(this);
        
        }
        
    }

    /**
     * @author Andreas Scalas
     * 
     * The GET method for timeElapsed
     * @return
     */
	public long getTimeElapsed() {
		
		return timeElapsed;
		
	}
    
	/**
	 * @author Andreas Scalas
	 * 
	 * Method that overrides the onDraw method from the parent.
	 */
	@Override
	protected void onDraw(Canvas canvas){
		
		super.onDraw(canvas); //Calls the onDraw method of the parent.
		
		int height, width; //Dimensions of the canvas.
		int red, green, blue; //Color intensities of the text.
		int pos, tmp = textColor; //Temporary variables for position and color.
		int shift;
		//Size definition.
		height = canvas.getHeight(); //Obtains the height of the canvas.
		width = canvas.getWidth(); //Obtains the width of the canvas.
		
 		pos = (int) (x + (width - TEXT_WIDTH) / 2 + SHIFT); //Defines starting point for the text.
 		
 		//Color settings.
		red = textColor/((int) Math.pow(HEXADECIMAL_BASE, RED_POSITION)); //Obtains red chromatic component of textColor.
		textColor = textColor % (int) Math.pow(HEXADECIMAL_BASE, RED_POSITION); //Deletes red chromatic component from textColor.
		green = textColor / (int) Math.pow(HEXADECIMAL_BASE, GREEN_POSITION); //Obtains green chromatic component of textColor.
		textColor = textColor % (int) Math.pow(HEXADECIMAL_BASE, GREEN_POSITION); //Deletes green chromatic component from textColor.
		blue = textColor; //The blue chromatic component is what remains in textColor. 
		textColor = tmp; //Restore textColor.
		paint.setColor(Color.argb(OPACITY, red, green, blue)); //Set the color of the external circle of the button.
		
		
		//Design of the first text component (hours).
		paint.setTextSize(INIT_DIMENSION); //Sets the text size. 
        shift = (int) ((int) paint.getTextSize()*SHIFT_RATIO); //Set the shift.
		y = height / 2 + shift;
		paint.setTextAlign(Align.CENTER); //Sets the text alignement.
		canvas.drawText(textHours, (float) pos, (float) height / 2 + shift, paint); //Draws the first text component.
		pos = pos + HOUR_MINUTE_WIDTH; //Updates the position on which the text will be drawn.
		
		//Design of the second text component (minutes).
		paint.setTextSize(INIT_DIMENSION); //Sets the text size. 
		paint.setTextAlign(Align.CENTER); //Sets the text alignement.
		canvas.drawText(textMinutes, (float)pos, (float) y, paint); //Draws the second text component.
		pos = pos + HOUR_MINUTE_WIDTH; //Updates the position on which the text will be drawn.
		
		//Design of the third text component (seconds).
		paint.setTextSize(INIT_DIMENSION); //Sets the text size. 
		paint.setTextAlign(Align.CENTER); //Sets the text alignement.
		canvas.drawText(textSeconds, (float)pos, (float)height / 2 + shift, paint); //Draws the second text component.
		pos = pos + SECOND_WIDTH; //Updates the position on which the text will be drawn.
		
		//Design of the fourth text component (milliseconds).
		paint.setTextSize(INIT_DIMENSION_MILLISECONDS); //Sets the text size. 
		paint.setTextAlign(Align.CENTER); //Sets the text alignement.
		canvas.drawText(textMilliSeconds, (float) pos, (float) height / 2 + shift, paint); //Draws the second text component.
		
		paint.reset();
		
	}

	/**
	 * @author Andreas Scalas
	 * 
	 * @return
	 */
	public boolean isRunning() {
		
		return mRunning;
		
	}

	/**
	 * @author Andreas Scalas
	 * @param mRunning
	 */
	public void setmRunning(boolean mRunning) {
		
		this.mRunning = mRunning;
		
	}
}